﻿Imports System
Imports System.Collections.Generic
Imports System.Text
Imports System.Xml.Serialization

Public Module HelperClasses

    Public Function IndexByChar(Strings As IEnumerable(Of String)) As SortedDictionary(Of Char, List(Of String))
        Dim Index As New SortedDictionary(Of Char, List(Of String))
        For Each title In Strings
            Dim c As Char = title(0)

            If Char.IsLetter(c) Then
                c = Char.ToUpper(c)
            ElseIf Char.IsDigit(c) Then
                c = "#"c
            Else
                c = "?"c
            End If
            If Not Index.ContainsKey(c) Then Index.Add(c, New List(Of String))
            Index(c).Add(title)
        Next
        Return Index
    End Function

    Public Function ReadDataDir()
        Dim reader As New IO.StreamReader(HttpRuntime.AppDomainAppPath & "WikiDataDir.config")
        Dim dataDir As String = reader.ReadLine()
        reader.Close()
        Return datadir
    End Function

    <XmlRoot("dictionary")> _
    Public Class SerializableDictionary(Of TKey, TValue)
        Inherits Dictionary(Of TKey, TValue)
        Implements System.Xml.Serialization.IXmlSerializable

        Private ns As New XmlSerializerNamespaces()

        Public Sub New()
            MyBase.New()
            ns.Add("", "")
        End Sub

        Public Function GetSchema() As System.Xml.Schema.XmlSchema Implements IXmlSerializable.GetSchema
            Return Nothing
        End Function

        Public Sub ReadXml(ByVal reader As System.Xml.XmlReader) Implements IXmlSerializable.ReadXml
            Dim keySerializer As XmlSerializer = New XmlSerializer(GetType(TKey))
            Dim valueSerializer As XmlSerializer = New XmlSerializer(GetType(TValue))
           

            Dim wasEmpty As Boolean = reader.IsEmptyElement
            reader.Read()

            If (wasEmpty) Then Return

            While (reader.NodeType <> System.Xml.XmlNodeType.EndElement)

                reader.ReadStartElement("item")

                reader.ReadStartElement("key")

                Dim key As TKey = CType(keySerializer.Deserialize(reader), TKey)

                reader.ReadEndElement()

                reader.ReadStartElement("value")

                Dim value As TValue = CType(valueSerializer.Deserialize(reader), TValue)

                reader.ReadEndElement()

                Me.Add(key, value)

                reader.ReadEndElement()

                reader.MoveToContent()

            End While

            reader.ReadEndElement()

        End Sub

        Public Sub WriteXml(ByVal writer As System.Xml.XmlWriter) Implements IXmlSerializable.WriteXml

            Dim keySerializer As New XmlSerializer(GetType(TKey), "")
            Dim valueSerializer As New XmlSerializer(GetType(TValue), "")

            For Each key As TKey In Me.Keys

                writer.WriteStartElement("item")

                writer.WriteStartElement("key")

                keySerializer.Serialize(writer, key, ns)

                writer.WriteEndElement()

                writer.WriteStartElement("value")

                Dim value As TValue = Me(key)

                valueSerializer.Serialize(writer, value, ns)

                writer.WriteEndElement()

                writer.WriteEndElement()

            Next key

        End Sub

    End Class

    Public Class StringSet
        Inherits HashSet(Of String)

        Public Overrides Function ToString() As String
            Return String.Join(", ", Me.ToArray)
        End Function

        Public Sub New()
            MyBase.New()
        End Sub

        Public Sub New(stringSet As SortedStringSet)
            MyBase.New(CType(stringSet, IEnumerable(Of String)))
        End Sub
        Public Sub New(stringSet As StringSet)
            MyBase.New(CType(stringSet, IEnumerable(Of String)))
        End Sub

        Public Sub New(DelimeteredString As String)
            MyBase.New()
            If DelimeteredString Is Nothing Then Return
            Dim vals = DelimeteredString.Split({","c, ";"c, CChar(vbCr), CChar(vbLf)})
            For Each v In vals
                v = v.Trim()
                If v.Length > 0 Then Add(v)
            Next
        End Sub

        Public Shared Narrowing Operator CType(ByVal str As String) As StringSet
            Return New StringSet(str)
        End Operator
    End Class
    Public Class SortedStringSet
        Inherits SerializableSortedSet(Of String)

        Public Overrides Function ToString() As String
            Return String.Join(", ", Me.ToArray)
        End Function

        Public Sub New()
            MyBase.New()
        End Sub

        Public Sub New(stringSet As SortedStringSet)
            MyBase.New(CType(stringSet, IEnumerable(Of String)))
        End Sub
        Public Sub New(stringSet As StringSet)
            MyBase.New(CType(stringSet, IEnumerable(Of String)))
        End Sub

        Public Sub New(DelimeteredString As String)
            MyBase.New()
            If DelimeteredString Is Nothing Then Return
            Dim vals = DelimeteredString.Split({","c, ";"c, CChar(vbCr), CChar(vbLf)})
            For Each v In vals
                v = v.Trim()
                If v.Length > 0 Then Add(v)
            Next
        End Sub

        Public Shared Narrowing Operator CType(ByVal str As String) As SortedStringSet
            Return New SortedStringSet(str)
        End Operator
    End Class

    Public Class SerializableSortedSet(Of T)
        Inherits SortedSet(Of T)
        Implements Runtime.Serialization.ISerializable
        Implements ICollection(Of T)

        Default Public Property Item(Index As Integer) As T
            Get
                Return Me.ElementAt(Index)
            End Get
            Set(value As T)
                If Index <> Me.Count Then Throw New Exception("You can only add an item to the end of the sorted set. It cannot be inserted at a specific index.")
                Me.Add(value)
            End Set
        End Property

        Public Overloads Sub Add(Item As T) Implements ICollection(Of T).Add
            MyBase.Add(Item)
        End Sub

        Public Sub New(Enumerable As IEnumerable(Of T))
            MyBase.New(Enumerable)
        End Sub
        Public Sub New()
            MyBase.New()
        End Sub
    End Class

    Public Function isLocalHost(host As String)
        Return {"localhost", "loopback", "127.0.0.1", "::1"}.Contains(host.ToLower())
    End Function
    Public Function ProcessImageUrl(PictureUrl As String, Optional hostname As String = "", Optional preferedWidth As Integer = 0, Optional preferedHeight As Integer = 0)
        Dim ProcessedImageUrl As String = PictureUrl.Replace("\", "/").Replace("'", "%27").Replace("""", "%22")

        Dim isLocalImage As Boolean = Not Regex.IsMatch(ProcessedImageUrl, "^(www.|https\:|http\:).*", RegexOptions.IgnoreCase)

        If isLocalImage Then

            If Not IO.Path.HasExtension(ProcessedImageUrl) Then
                ProcessedImageUrl &= ".jpg"
            End If

            If Not ProcessedImageUrl.StartsWith("/images/", StringComparison.InvariantCultureIgnoreCase) Then
                If ProcessedImageUrl.StartsWith("images/", StringComparison.InvariantCultureIgnoreCase) Then
                    ProcessedImageUrl = "/" & ProcessedImageUrl
                Else
                    ProcessedImageUrl = "/images/" & ProcessedImageUrl
                End If
            End If

            If Not isLocalHost(hostname) And (preferedWidth > 0 Or preferedHeight > 0) Then
                ProcessedImageUrl &= "?mode=max"
                If preferedWidth > 0 Then
                    ProcessedImageUrl = ProcessedImageUrl & "&width=" & preferedWidth
                End If
                If preferedHeight > 0 Then
                    ProcessedImageUrl = ProcessedImageUrl & "&height=" & preferedHeight
                End If
            End If

        End If

        Return ProcessedImageUrl
    End Function

    Public MustInherit Class MarkupTools

        Private Class EmptyViewDataContainer
            Implements IViewDataContainer
            Public Property ViewData As New ViewDataDictionary Implements IViewDataContainer.ViewData
        End Class

        'Private MustInherit Class MarkupProcessor
        '    Public MustOverride Function process(ByRef markup As String, pageModel As WikiPageModel, context As ViewContext) As String
        'End Class
        'Private Class HeaderProcessor
        '    Inherits MarkupProcessor

        '    Private regex As New Regex("^(?:\<p\>)?[ \t]*?(=+)\s*(\S*?)\s*\1[ \t]*?$")

        '    Public Overrides Function process(ByRef markup As String, pageModel As WikiPageModel, context As ViewContext) As String
        '        Dim lines As String() = markup.Split(vbLf)
        '        Dim builder As New StringBuilder
        '        For i = 0 To lines.Length - 1
        '            Dim match = regex.Match(lines(i))
        '            If match.Success Then
        '                Dim titleLevel = match.Groups(1).Length + 2
        '                titleLevel = Math.Min(titleLevel, 6)
        '                builder.Append(String.Format("</p>{2}<h{0} id='{1}'> {1} </h{0}>{2}<p>", titleLevel, match.Groups(2).Value, vbLf))
        '            Else
        '                builder.AppendLine(lines(i))
        '            End If
        '        Next
        '        Return builder.ToString
        '    End Function
        'End Class
        'Private MustInherit Class BracketProcessor
        '    Inherits MarkupProcessor

        '    Protected MustOverride Property splitRegex As Regex
        '    Private linkRegex As New Regex("^([^\|]+)(\|(.+))?$")

        '    Public Overrides Function process(ByRef markup As String, pageModel As WikiPageModel, context As ViewContext) As String
        '        Dim htmlHelper As New HtmlHelper(context, New EmptyViewDataContainer)
        '        Dim parts = splitRegex.Split(markup)
        '        For i = 1 To parts.Length - 1 Step 2
        '            Dim match = linkRegex.Match(parts(i))
        '            Dim target = match.Groups(1).Value.Trim()
        '            Dim display = target
        '            If match.Groups(2).Success Then
        '                display = match.Groups(3).Value.Trim()
        '            End If
        '            parts(i) = CreateContent(target, display, pageModel, htmlHelper)
        '        Next
        '        Return Strings.Join(parts)
        '    End Function

        '    Protected MustOverride Function CreateContent(target As String, display As String, pageModel As WikiPageModel, htmlHelper As HtmlHelper) As String
        'End Class
        'Private Class InnerLinkProcessor
        '    Inherits BracketProcessor
        '    Protected Overrides Property splitRegex As New Regex("\[\[(.+?)\]\]")
        '    Protected Overrides Function CreateContent(target As String, display As String, pageModel As WikiPageModel, htmlHelper As HtmlHelper) As String
        '        Return WikiModel.CreateInnerLink(target, display, pageModel.Title, htmlHelper)
        '    End Function

        'End Class
        'Private Class ExternalLinkProcessor
        '    Inherits BracketProcessor
        '    Protected Overrides Property splitRegex As New Regex("\[(.+?)\]")
        '    Protected Overrides Function CreateContent(target As String, display As String, pageModel As WikiPageModel, htmlHelper As HtmlHelper) As String
        '        Dim fullTarget As String = target
        '        If Not (target.StartsWith("/") OrElse target.StartsWith("http://")) Then
        '            fullTarget = "http://" & target
        '        End If
        '        Return String.Format("<a href='{0}' title='{1}'>{2}</a>", fullTarget, target, display)
        '    End Function
        'End Class

        'Private Class CommentProcessor
        '    Inherits BracketProcessor
        '    Protected Overrides Property splitRegex As New Regex("\(\((.+?)\)\)")
        '    Protected Overrides Function CreateContent(target As String, display As String, pageModel As WikiPageModel, htmlHelper As HtmlHelper) As String
        '        Return String.Format("<span class='comment'>({0})</span>", display)
        '    End Function

        'End Class

        'Private Class FontDecorationProcessor
        '    Inherits MarkupProcessor

        '    Private markupSymbols As String() = {"\*", "'", "_", "~"}
        '    Private htmlTags As String() = {"strong", "em", "u", "del"}
        '    Private regexes(markupSymbols.Length - 1) As Regex

        '    Sub New()
        '        If markupSymbols.Length <> htmlTags.Length Then Throw New Exception("Assertion error in FontDecorationProcessor")
        '        For i = 0 To markupSymbols.Length - 1
        '            regexes(i) = New Regex(String.Format("{0}{0}(.+?){0}{0}", markupSymbols(i)))
        '        Next
        '    End Sub

        '    Public Overrides Function process(ByRef markup As String, pageModel As WikiPageModel, context As ViewContext) As String
        '        For regexI = 0 To regexes.Length - 1
        '            Dim parts = regexes(regexI).Split(markup)
        '            For partI = 1 To parts.Length - 1 Step 2
        '                Dim match = regexes(regexI).Match(parts(partI))
        '                parts(partI) = String.Format("<{0}>{1}</{0}>", htmlTags(regexI), parts(partI))
        '            Next
        '            markup = Strings.Join(parts)
        '        Next
        '        Return markup
        '    End Function
        'End Class
        'Private Class ImageProcessor
        '    Inherits MarkupProcessor

        '    Private regex As New Regex("\{\{([^\|]+?)(?:\|(?'width'\d+?)(?:\|(?'height'\d+?))?)?(?:\|(?'align'(:?left|right|center)))?(?:\|(?'caption'.+?))?\}\}", RegexOptions.Multiline)
        '    Public Overrides Function process(ByRef markup As String, pageModel As WikiPageModel, context As ViewContext) As String
        '        Return regex.Replace(markup, "<div class='image-${align}''><a href='$1'><img src='$1' width='${width}' height='${height}'></a><p class='image-caption'>${caption}</p></div>")
        '    End Function
        'End Class
        'Private Class ListProcessor
        '    Inherits MarkupProcessor

        '    Private regex As New Regex("^[ \t]*?([\*#]+)[ \t]*(.*?)[ \t]*$", RegexOptions.Multiline)
        '    Public Overrides Function process(ByRef markup As String, pageModel As WikiPageModel, context As ViewContext) As String
        '        Dim builder As New StringBuilder

        '        Dim parts = regex.Split(markup)

        '        If parts.Length = 1 Then
        '            Return String.Format("<p>{0}</p>", parts(0))
        '        End If

        '        Dim level As Integer = 0

        '        For i = 2 To parts.Length - 1 Step 3
        '            If parts(i - 2) <> vbLf Then
        '                ' Close lists
        '                For c = 0 To level - 1
        '                    builder.AppendLine("</ul>")
        '                Next
        '                level = 0
        '                builder.Append("<p>")
        '                builder.Append(parts(i - 2))
        '                builder.AppendLine("</p>")
        '            End If

        '            Dim newLevel = parts(i - 1).Length
        '            If level < newLevel Then
        '                For c = level To newLevel - 1 Step 1
        '                    builder.AppendLine("<ul>")
        '                Next
        '            Else
        '                For c = level To newLevel + 1 Step -1
        '                    builder.AppendLine("</ul>")
        '                Next
        '            End If
        '            level = newLevel
        '            builder.Append("<li>")
        '            builder.Append(parts(i))
        '            builder.AppendLine("</li>")
        '        Next
        '        Return builder.ToString()
        '    End Function


        '    End Class
        'Private Class EmptyParagrapghProcessor
        '    Inherits MarkupProcessor

        '    Private regex As New Regex("\<p\>\s*\<\/p\>", RegexOptions.Multiline)
        '    Public Overrides Function process(ByRef markup As String, pageModel As WikiPageModel, context As ViewContext) As String
        '        Return regex.Replace(markup, "")
        '    End Function
        'End Class

        'Private Shared MarkupProcessors As MarkupProcessor() = {New ListProcessor, New HeaderProcessor, New InnerLinkProcessor, New ExternalLinkProcessor, New CommentProcessor,
        '                                                        New FontDecorationProcessor, New ImageProcessor, New EmptyParagrapghProcessor}


        Public Shared Function ProcessExtraMarkupHtml(htmlWithMarkup As String) As String

            htmlWithMarkup = Regex.Replace(htmlWithMarkup, "\(\((.*?)\)\)", "<span class=""comment"">($1)</span>")
            htmlWithMarkup = Regex.Replace(htmlWithMarkup, "\[\[(.*?)\]\]", "<a href=""$1"">$1</a>")

            htmlWithMarkup = Regex.Replace(htmlWithMarkup, "style ?= ?[""'].*?[""']", "") 'remove element styling

            Return htmlWithMarkup
        End Function


        Private Shared Function ReplaceLink(ByVal match As Match, pageModel As WikiPageModel, htmlHelper As htmlHelper) As String
            If Regex.IsMatch(match.Groups(2).Value, "https?:") Then Return match.Value
            Return WikiModel.CreateInnerLink(match.Groups(2).Value, match.Groups(3).Value, pageModel.Title, htmlHelper)
        End Function
        Private Shared Function ReplaceImage(ByVal match As Match, htmlHelper As htmlHelper) As String

            Dim matches = Regex.Matches(match.Groups(1).Value, " +(\w*) *?= *?[""'](.*?)[""']")
            Dim b As New StringBuilder
            Dim caption As String = ""
            Dim originalTarget As String = ""
            Dim width As Integer = Nothing
            Dim height As Integer = Nothing
            b.Append(" src=""{0}""")
            For Each m As Match In matches
                If m.Groups(1).Value = "src" Then
                    originalTarget = m.Groups(2).Value
                Else
                    If m.Groups(1).Value = "alt" Then
                        caption = m.Groups(2).Value
                    ElseIf m.Groups(1).Value = "width" Then
                        width = CInt(m.Groups(2).Value)
                    ElseIf m.Groups(1).Value = "height" Then
                        height = CInt(m.Groups(2).Value)
                    End If
                    b.Append(String.Format(" {0}=""{1}""", m.Groups(1), m.Groups(2)))
                End If
            Next

            Dim hostname As String = htmlHelper.ViewContext.RequestContext.HttpContext.Request.Url.Host
            If width = 0 And height = 0 Then
                width = 400
                height = 300
            End If
            Dim imgAttrs As String = String.Format(b.ToString(), ProcessImageUrl(originalTarget, hostname, width, height))

            Return String.Format("<figure><a href=""{0}""><img {1} ></a><figcaption>{2}</figcaption></figure>", ProcessImageUrl(originalTarget), imgAttrs, caption)
        End Function
        Private Shared Function ReplaceHeader(ByVal match As Match, htmlHelper As htmlHelper) As String
            Return String.Format("<{0} id=""{2}"">{1}</{0}>", match.Groups(1).Value, match.Groups(2).Value, Regex.Replace(match.Groups(2).Value, "<.*?>", ""))
        End Function
        Public Function BuildPageTOC(page) As String
            'TODO
        End Function

        Public Shared Function ProcessPageHtmlContent(html As String, pageModel As WikiPageModel, htmlHelper As htmlHelper) As String
            'Link
            Dim LinkReplaceLinkEvaluator As New MatchEvaluator(Function(match As Match) ReplaceLink(match, pageModel, htmlHelper))
            html = Regex.Replace(html, "< *a.*?href *= *([""'])(.*?)\1.*?>(.*?)</ *a *>", LinkReplaceLinkEvaluator, RegexOptions.IgnoreCase)

            'Image
            Dim ImageReplaceLinkEvaluator As New MatchEvaluator(Function(match As Match) ReplaceImage(match, htmlHelper))
            html = Regex.Replace(html, "< *img(.*?)>", ImageReplaceLinkEvaluator, RegexOptions.IgnoreCase)

            'Headers
            Dim HeaderReplaceLinkEvaluator As New MatchEvaluator(Function(match As Match) ReplaceHeader(match, htmlHelper))
            html = Regex.Replace(html, "< ?(h\d).*?>\s*(.*?)\s*< ?/ ?\1 ?>", HeaderReplaceLinkEvaluator, RegexOptions.IgnoreCase)

            'div
            'html = Regex.Replace(html, "< ?(h\d).*?>\s*(.*?)\s*< ?/ ?\1 ?>", "<$1 id=""$2"">$2</$1>")

            Return html
        End Function

    End Class


    Public MustInherit Class TagSearchUtils

        Public Enum Importance
            MustInclude
            StrongInclude
            WeakInclude
            WeakExclude
            StrongExclude
            MustExclude
        End Enum

        Public Function Rate(pages As Dictionary(Of String, Integer), tags As String, importance As Importance) As Dictionary(Of String, Integer)
            Dim foundPages As New List(Of String)
            For Each tag In tags
                foundPages.AddRange(Wiki.WikiModel.Instance.GetPagesByTag(tag))
            Next

            Dim changeIfTagged As Integer = 0
            Dim changeIfNotTagged As Integer = 0

            Select Case importance
                Case TagSearchUtils.Importance.MustInclude
                    changeIfNotTagged = -1000
                Case TagSearchUtils.Importance.StrongInclude
                    changeIfNotTagged = -10
                Case TagSearchUtils.Importance.WeakInclude
                    changeIfNotTagged = -3
                Case TagSearchUtils.Importance.MustExclude
                    changeIfTagged = -1000
                Case TagSearchUtils.Importance.StrongExclude
                    changeIfTagged = -5
                Case TagSearchUtils.Importance.WeakExclude
                    changeIfTagged = -3
            End Select

            For Each Page In pages.Keys
                If foundPages.Contains(Page) Then
                    pages(Page) += changeIfTagged
                Else
                    pages(Page) += changeIfNotTagged
                End If
            Next
            Return pages

        End Function

        Public Function Filter(pages As Dictionary(Of String, Integer), Optional minRating As Integer = -500) As Dictionary(Of String, Integer)
            Return pages.Where(Function(pair) pair.Value > minRating)
        End Function
        Public Function GetTop(pages As Dictionary(Of String, Integer), count As Integer) As List(Of String)
            Dim minScore As Integer = Integer.MinValue

            Dim sorted = From pair In pages Order By pair.Value
            Dim top As New List(Of String)
            count = Math.Min(count, sorted.Count)
            For Each ratedPage In sorted
                If count = 0 Then Exit For
                top.Add(ratedPage.Key)
                count -= 1
            Next
            Return top
        End Function

    End Class

End Module

